home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
Amiga Mag HDD Backup
/
Amiga Mag HDD Backup.zip
/
Amiga Mag HDD Backup
/
Alexander.img.bin
/
Alexander.img
/
***9.11 All NEWer important
/
Stockman⁄StructDraw Post
/
PSlang.asc
< prev
next >
Wrap
Text File
|
1994-09-16
|
37KB
|
686 lines
Creating Structured Drawings - Part One: PostScript
The Amiga has always been known as a powerful graphics computer. Creating complex paintings and
drawings is almost trivial. The early introduction of the ILBM IFF standard for storing bitmap graphics
by Electronic Arts eased the process of sharing bitmap graphic images. Unfortunately, sharing
structured drawing images between programs has not been as easy. There is no one standard format that
is widely used by all Amiga programs. This causes headaches for users, and requires the programmer to
support a number of structured drawing formats. Finding information about the commonly used formats
and learning their syntax requires considerable effort. This article is the first of a series that will discuss
two commonly used structured drawing formats. It is hoped that by providing references and simple
examples, these formats will be included in more programs.
Structured drawings and bitmap drawings may appear the same on screen, but they are stored quite
differently. A structured drawing is best used for line art and complex figures, while bitmap images are
better for scanned photographs and artwork composed of many colors. Each element (e.g. line,
rectangle, circle, etc.) of a structured drawing is saved as an equation that very precisely defines that
element. Therefore, when that drawing is printed to a device that has a higher resolution than the screen,
each element is recreated from the original equation to take advantage of the highest resolution possible.
This avoids the dreaded "jaggies". A drawing saved as a bitmap does not save specific information
about each element of a drawing. A bitmap contains information about the color of each pixel on screen.
Therefore, if your screen image is 640x400 pixels (75 dpi commonly), printing that image to a 300 dpi
printer (2560x3300 printer dots) either results in a very small image (~2x1.34 inches using 300 dpi dots),
or a larger image composed of big jaggy dots. Because bitmaps contain the color information for every
pixel, they are best suited for images that have many colors of similar hues.
In this and the following series of articles, I will discuss the basics of two structured drawing formats.
These are PostScript (Adobe Systems), and HP-GL/2 (Hewlett-Packard). The IFF DR2D format is not
discussed in this series because that task can be better handled by its creator (Stylus). I have included a
C program called StructDrawing.c that demonstrates some of the concepts in these articles. Although I
do not discuss the DXF (AutoDesk) format in the text, StructDrawing.c includes rudimentary code for
creating DXF files. This is a structured drawing format commonly using by MS-DOS-based machines
and by CAD programs. Programmers interested in supporting the DXF format should contact AutoDesk
for a copy of the DXF guidelines (415/491-8736). Compared to the documentation available for
PostScript and HP-GL/2, I found the DXF documentation very limited.
Given the complexity of these formats, I cannot cover the entire scope of each standard in one or two
articles. My aim is to introduce the basics of each format to 1) point the user in the correct direction for
further study, and 2) provide simple C code examples of how to create structured drawings using each
format. References for further study of each format are provided.
PostScript
PostScript is probably the best known and most commonly used format for saving structured drawings.
Calling PostScript a structured drawing format is not quite accurate and almost belittling. PostScript is
actually a rich and complex programming language that is first and foremost a page description
language. Its handling of text has made it the most commonly used format for desktop publishing.
Structured graphics are often saved in Encapsulated PostScript (EPS) format. EPS format is a PostScript
file with a few extra commands. Because this article is about structured drawings, it will focus on the
graphics object commands and not so much on the many programming and text commands.
From a beginning programmer's perspective, probably the best property of PostScript files is that they
are simple ASCII text. This means a PostScript structured drawing can be created using a text editor.
For example, to create a line from the lower left corner of a page to the upper right corner of a page, the
following text can be entered into a text editor, saved, and then sent to a PostScript capable device.
newpath
72 72 moveto
576 720 lineto
stroke
showpage
newpath clears any previously defined graphics path. 72 72 moveto moves an imaginary graphics pen
that is raised off the paper to X position 72 and Y position 72 (the significance of 72 will be discussed
shortly). 576 720 lineto puts the pen down on the paper at the pen's current location (set by the moveto
command) and draws an imaginary line to point 576, 720 on the page. The drawn line is called
imaginary because the line is not drawn on the page until the stroke command is received. showpage
completes all drawing to the current page and either displays the page or outputs the paper. If the user
deletes the stroke command, the line will not be drawn on the paper, resulting in a blank page. The
lineto and moveto commands are considered path commands. They define a specific path, but they do
not apply "ink" to the page. Applying ink to the page occurs by use of the stroke or fill operators.
As with most things in life, learning PostScript is best achieved by practice. Amiga users are fortunate
to have two freely distributable programs, Post and GhostScript, that can interpret PostScript code and
either draw it to the screen or to a Preferences supported printer. The method I found useful was to have
the PostScript interpreter running in the background while I created simple PostScript code in an editor
(Ed or memacs will do). The PostScript code is saved to a file, loaded into the interpreter, and then
drawn to screen by the interpreter. This gives immediate feedback while not wasting paper. I also did
not have a PostScript-capable printer when I learned PostScript.
The PostScript language uses a stack and relies on postfix notation. A stack is an area of memory that
acts kind of like a stack of books. As one book is placed on top of another, the most accessible book
will always be the book on top of the stack. In other words, the last book on the stack is the first book
that will come off the stack. A simple example in PostScript would be the addition of two numbers, 72
144 add. 72 is placed on the stack first, then 144, and finally the results of the addition 216 would be at
the top of the stack and most accessible for other operations.
Postfix notation refers to the fact that the parameters precede the command as in 72 144 moveto.
Performing a similar function using an Amiga graphics command appears as Move(rp, 72, 144). The C
programming language and the Amiga graphics commands do not use postfix notation. Forth
programmers will feel right at home with PostScript, while the rest of us have to keep reminding
ourselves what this "backwards" way of doing things really means. For the simple graphics commands
we will look at, understanding the stack and postfix notation concepts are not essential. The
programmer must just remember that the parameters precede the command. For PostScript
programmers who write PostScript functions, understanding the stack and postfix notation is essential.
The PostScript language is page oriented. It works on only one page at a time. Each page has on it, a
user coordinate system. The default user coordinate space has 72 units per inch. Position 0, 0, or the
page's origin, is located at the lower left corner of the page. Moving to the right increases the X, or
horizontal, value by 72 units for every inch moved. Moving up on the page increases the Y value by 72
units for each inch. Therefore, the upper right corner of an 8.5x11 inch page is location 612, 792. The
resolution of a PostScript image is not limited to 1/72's of an inch. Actually, the resolution is controlled
by the output device. An RIP equipped Linotronic can have a resolution finer than 1/2000th of an inch.
Therefore a command such as 72.125 144.500 moveto is perfectly acceptable.
Most humans do not think in terms of 1/72nd's of an inch. It is much more convenient to work in
decimal inches. For example, a distance of 1.5 inches is more easily understood than 108 units when
trying to place an object on a page. By creating a PostScript procedure, we can use inches in our
PostScript file and allow PostScript to convert our inch measurements into 72nd's of an inch. Let us
re-write our previous example to use inches instead of the default PostScript units.
/inch {72 mul} def
newpath
1.0 inch 1.0 inch moveto
8.0 inch 10.0 inch lineto
stroke
showpage
Our PostScript procedure is the first line "/inch {72 mul} def". Instead of saying 72 72 moveto as in
our first example, we can now write 1.0 inch 1.0 inch moveto. The PostScript interpreter places 1.0 on
the stack. When it encounters the word inch, it looks in its dictionary for that word. It finds that we
have defined the word inch to mean multiply the top value on the stack by 72 and place the results on the
stack (1.0 72 mul). If you prefer to work in centimeters, you can write a similar definition that performs
the same function.
/cm {28.346 mul} def
It is important to note that your definition only takes effect for all lines after the location of your
definition. In addition, do not equate a definition with a PostScript command word. For example, you
do not want to alter the meaning of moveto by re-defining it (e.g. /moveto {-1.0 mul} def). For the rest
of this article, we will work in inches and not the default PostScript defined values when referring to
page coordinates. We will still use the default 72nd's of an inch when discussing line thickness and a
text's point size (1 point equals approximately 1/72nd's of an inch).
The first example created a 1 point wide black line. PostScript makes it easy to alter the thickness and
darkness of a line. The thickness of a line is set by the command setlinewidth. The default line width is
one default page unit. To create a thick line, we would use setlinewidth with some number greater than
1 (e.g. 5 setlinewidth). To create a thinner line, we can write something like 0.25 setlinewidth. This
line width will stay in effect until another setlinewidth command is issued.
To change the level of gray of the line, we use the setgray command. The setgray command takes one
argument, the level of gray. 0 signifies black while 1 means white. The command 0.5 setgray produces
a medium gray line. This command stays in effect until another setgray command is received (or
another set color type command, e.g. setrgb).
We now know enough about PostScript programming to create a couple of functions that perform
similar operations to the Amiga's Move() and Draw() graphics commands. For many programming
projects, it may be most efficient to write each graphics function so that it can send graphics commands
to either an Amiga window or to a PostScript capable device. This method allows us to either send a
structured drawing to screen or to a PostScript capable device by changing only one variable. As a
simple example, here is a short C function that can either move the screen cursor or the PostScript
cursor, depending on where we want the output to go.
/* Global definitions */
#define SCREEN 0
#define PS 1
#define MAP_X(x) (10+(x)) /* offset some for left border */
#define MAP_Y(y) (384-(y)) /* 384 being height of drawing area */
#define MAP_PSX(x) (0.25+(PS_Width/W_Width * (x)))
#define MAP_PSY(y) (0.25+(PS_Height/W_Height*(W_Height-(y)))
/* Global variables */
UBYTE Output_Device= SCREEN;
struct RastPort *sd_rp=NULL;
FILE *Outfp=NULL;
/* call sd_Move() by first converting values to screen values, such as:
sd_Move(MAP_X(x), MAP_Y(y));
*/
void sd_Move(float x, float y){
if(Output_Device==SCREEN){
Move(sd_rp, (int)(x+0.5), (int)(y+0.5));
}
else if(Output_Device==PS){
fprintf(Outfp, "%f inch %f inch moveto\n", MAP_PSX(x),MAP_PSY(y));
}
return;
}
The #define MAP_ ... statements deal with scaling the drawing correctly for the selected output type.
The global variable Output_Device allows us to select where we want output sent. As we write more
functions, we will see that by changing one variable, Output_Device, we can redirect the drawing of a
complex graphic image to any number of output devices. This method may not work in all applications,
and the most efficient PostScript code is not created, but it has significant merit in terms of ease of
implementation and the range of formats that can be easily supported.
The next function draws a line from the current imaginary pen location to the user-defined new location.
void sd_Draw(float x, float y){
if(Output_Device==SCREEN){
Draw(sd_rp, (int)(x+0.5), (int)(y+0.5));
}
else if(Output_Device==PS){
fprintf(Outfp, "%f inch %f inch lineto\n", MAP_PSX(x), MAP_PSY(y));
}
return;
}
Because PostScript can control the thickness of a line while the Amiga graphics commands cannot, we
may want to write a function that will do this whether the line is drawn to the screen or to a PostScript
device.
void sd_DrawLine(int l_thick, float x1,float y1, float x2, float y2){
int i=0;
if(Output_Device==SCREEN){
for(i=0;i<l_thick;i++){
if(x1!=x2){ /* note, there are better algorithms than this to deal with */
/* correctly drawing variable line thickness */
Move(sd_rp, (int)(x1+0.5), (int)(y1+0.5+l_thick/2.-i));
Draw(sd_rp, (int)(x2+0.5), (int)(y2++0.5l_thick/2.-i));
}
else{
Move(sd_rp, (int)(x1+0.5+l_thick/2.-i), (int)(y1+0.5));
Draw(sd_rp, (int)(x2+0.5+l_thick/2.-i), (int)(y2+0.5));
}
}
}
else if(Output_Device==PS){
fprintf(Outfp,"%f setlinewidth\n",(float)(l_thick));
fprintf(Outfp,"%f inch %f inch moveto\n%f inch %f inchlineto\n", x1, y1, x2, y2);
}
return;
}
The next logical element to create using PostScript is a box or rectangle. The most basic method to
create a box is to use the moveto and lineto commands in combination to form the four sides. Here is a
simple example.
/inch {72 mul} def
newpath
5 inch 5 inch moveto
5 inch 6 inch lineto
6 inch 6 inch lineto
6 inch 5 inch lineto
5 inch 5 inch lineto
stroke
showpage
Because lines have thickness, the last side drawn that closed the box may have a little corner missing.
PostScript provides a command to deal with this blemish. Instead of using the last lineto command that
closed the box, we use the command closepath. This command will draw the last line segment and fill
in the blemish. The box we created has four black lines that form the sides and the center is white (i.e.
no ink).
To create a filled box, we use the same PostScript code as above, but we now substitute the stroke
command with the fill command. The result is a solid black box one inch square. As stated previously,
the lineto and moveto commands define a path, they do not draw lines. That is why when we just
substitute stroke (which strokes lines) with fill (which fills the defined path with the current pen) we get
an entirely different result.
By using the setgray command, it is possible to control the grayscale level of the fill. If we place the
command 0.75 setgray anywhere before the fill command we get a 1 inch square box that is a light
shade of gray. Looking closely at the box we just created, we realize there is no black outline along the
borders of the box. To create a bordered box that is filled with a light gray, we must first create the path
and fill it. Next, we recreate the same path, setgray to 0.0, and stroke it. If we reversed the order such
that we stroked first and then filled, the black outline is obliterated by the filled box. Overlapping
graphic images always hide whatever is underneath them. Therefore, if we create a 1 inch square
black-filled box, but then draw a white box at the same location and same size, our paper would remain
completely unmarked.
Our PostScript box function has hardcoded dimensions. Because a box is a commonly used geometric
shape, it is useful to write a function to create rectangles of any size, location, line thickness, and fill
type. We can create such a function by using the simple commands we have learned so far.
void sd_Box(float x1, float y1, float x2, float y2, int l_color, int f_color, int l_thick){
int i=0;
/* Make sure x1, y1 is upper left corner of box, required
by Amiga RectFill() command.
*/
if(x1>x2){ tmp=x1; x1=x2; x2=tmp;}
if(y1>y2){ tmp=y1; y1=y2; y2=tmp;}
if(Output_Device==SCREEN){
SetDrMd(sd_rp, JAM2);
SetAPen(sd_rp, f_color);
SetAfPt(sd_rp, pattern[f_pt], 2);
BNDRYOFF(sd_rp);
RectFill(sd_rp,(int)(x1+0.5),(int)(ymin+0.5),(int)(x2+0.5),(int)(y2+0.5));
for(i=0;i<l_thick;i++){
sd_Move((x1+i),(y1+1));
sd_Draw(l_thick,(x2-i),(y1+i));
sd_Draw(l_thick,(x2-i),(y2-i));
sd_Draw(l_thick,(x1+i),(y2-i));
sd_Draw(l_thick,(x1_i),(y1+i));
}
}
else if(Output_Device==PS){
/* first fill box */
gray_level=(GetRGB4(ssgcm,f_color))/4095.; /* AmigaDos call */
fprintf(Outfp,"newpath\n%.3f setgray\n", graylevel);
fprintf(Outfp,"%f inch %f inch moveto\n%f inch %f inch
lineto\n",MAP_PSX(x1),MAP_PSY(y1),MAP_PSX(x2),
MAP_PSY(y1));
fprintf(Outfp,"%f inch %f inch lineto\n%f inch %f inch lineto\n",
MAP_PSX(x2),MAP_PSY(y2),MAP_PSX(x1),MAP_PSY(y2));
fprintf(Outpf,"closepath\nfill\n");
/* then outline box */
gray_level=(GetRGB4(ssgcm,l_color))/4095.; /* AmigaDos call */
fprintf(Outfp,"newpath\n%.3f setgray\n%f setlinewidth\n", graylevel, (float)l_thick);
fprintf(Outfp,"%f inch %f inch moveto\n%f inch %f inch
lineto\n",MAP_PSX(x1),MAP_PSY(y1),MAP_PSX(x2),
MAP_PSY(y1));
fprintf(Outfp,"%f inch %f inch lineto\n%f inch %f inch lineto\n",
MAP_PSX(x2),MAP_PSY(y2),MAP_PSX(x1),MAP_PSY(y2));
fprintf(Outpf,"closepath\nstroke\n");
}
return;
}
PostScript offers significant control over text manipulation. For the purpose of this article we will only
discuss two basic aspects of creating textual elements. The first aspect is how to select a font family and
scale it to a desired size. The second aspect involves the actual printing of text on a PostScript-generated
image.
Most PostScript devices have one or more families of font typefaces hard-coded into the printer's ROM.
The more common typefaces resident in modern PostScript printers include Times-Roman, Helvetica,
and Courier. The first step in selecting a typeface for use is to locate the typeface in the PostScript font
dictionary. If the exact font name provided is present on the printer, the name is placed on the stack.
For example, to locate the Times-Roman typeface the following findfont command is entered:
/Times-Roman findfont
The size of the selected typeface is set by use of the scalefont command. The one parameter for this
command is the desired height of the characters in points. As always, the parameter precedes the
command. To scale the Times-Roman font to 12 points, the following command is entered:
12 scalefont
Finally, to make this current typeface and size the current default we use the setfont command. These
three commands can be written on separate lines or on the same line as:
/Times-Roman findfont 12 scalefont setfont
This typeface and size will remain the default until another setfont command is issued. Please note that
the typeface name given must correspond exactly to the name of the typeface as it is defined in the
PostScript device.
A simple C function can be written to set the current font family and size. As before, the global variable,
Output_Device, determines which commands to use.
void get_font(UBYTE *F_Name, float F_Sz){
if(Output_Device==SCREEN){
tAttr.ta_Name=F_Name;
tAttr.ta_YSize=(int)F_sz;
tAttr.ta_Flags=FPF_ROMFONT | FPF_DISKFONT;
if(!(tFont=(struct TextFont *) OpenDiskFont(&tAttr))){
printf("Cannot open font %s!\nUsing Topaz font!\n", F_Name);
tAttr.ta_Name="topaz.font";
if(!(tFont=(struct TextFont *)OpenDiskFont(&tAttr))) return;
}
SetFont(sd_rp, tFont);
}
else if(Output_Device==PS){
fprintf(Outfp,"/%s findfont %.3f scalefont setfont\n",F_Name, F_Sz);
}
return;
}
To actually draw a text string we use the show command. show is a painting operator like the
previously discussed stroke and fill operators. The simplest example of painting text involves preceding
the show command with the desired text string enclosed in parentheses. For example, to print the text
"Hello, World", the following show command would be issued:
newpath
/Times-Roman findfont 12 scalefont setfont
1 inch 1 inch moveto
(Hello, World) show
showpage
The text is printed at the current cursor location. The show command is similar to the Amiga's Text()
function. We can write a simple function that allows the printing of text to either the screen or to a
PostScript-generated page.
void sd_Text(char *str, int color){
float gray_level=0.0;
if(output_Device==SCREEN){
SetDrMd(sd_rp, JAM2);
SetAPen(sd_rp, color);
Text(sd_rp, str, strlen(str));
}
else if(Output_Device==PS){
gray_level=(GetRGB4(ssgcm,color))/4095.; /* AmigaDos call */
fprintf(Outfp,"%f setgray\n(%s) show\n", gray_level, str);
}
return;
}
Creating circles and arcs using PostScript is fairly simple by use of the arc command. The format of the
arc command is as follows:
xloc yloc radius startangle endangle arc
The xloc and yloc parameters determine the location of the center of the circle. The radius parameter
sets the size of the circle/arc's radius using the current page units. Both the startangle and endangle
parameters are in degrees. The circle/arc is drawn in a counterclockwise direction starting on the
positive X axis. The related arcn command draws the circle in a clockwise direction.
To create a circle with a center at 4.25, 5.5 inches and a radius of 2 inches, the following arc command
is used:
4.25 inch 5.5 inch 2. inch 0 360 arc
The arc command is a path command just like the lineto command. Therefore, to actually draw the
circle/arc, a stroke or fill command must be issued following the arc or arcn command.
If a current pen location is already defined when the arc command is issued, a line is drawn from the
current pen location to the location of the arc at the startangle. Using this fact, it is a simple matter to
create a pie slice by using the moveto, closepath, and arc commands. We first set the current pen
location to the center of the arc's radius by using the moveto command. Next, we draw the arc. Finally,
we use the closepath command to draw a line from the end of the arc back to the center of the arc.
newpath
2 inch 2 inch moveto
2 inch 2 inch 1 inch 45 120 arc
closepath
stroke
showpage
Unfortunately, the Amiga's graphics library does not have a command to draw part of a circle, i.e. arc.
The current commands are limited to DrawEllipse() and DrawCircle(). We can write our own arc type
command for use on Amiga screens/windows. The first step in creating an arc() function is to create a
function to draw polygons. An arc can basically be thought of as a polygon with many very short sides.
A simple polygon function that draws only an outline can be created using a few simple commands. For
the Amiga side, the outline of a polygon requires use of only the Move() and Draw() functions. For the
PostScript side, we really only need the moveto and lineto commands. Filling the polygon with various
patterns or colors may take significantly more work (see StructDraw.c). The simplest polygon function
accepts an array of X, Y data pairs that define the polygon's vertices. This is really all that is necessary
to create the polygon's outline. An example function follows.
void sd_Polygon(float *x, float *y, int ptsz, USHORT l_color){
int i=0;
if(Output_Device==SCREEN){
SetAPen(sd_rp, l_color); /* set line color */
Move(sd_rp, (int)(x[0]+0.5), (int)(y[0]+0.5));
for(i=1;i<ptsz;i++) Draw(sd_rp, (int)(x[i]+0.5), (int)(y[i]+0.5));
}
else if(Output_Device==PS){
fprintf(Outfp,"newpath\n%.3f inch %.3f inch moveto\n", MAP_PSX(x[0]),
MAP_PSY(y[0]));
for(i=1;i<ptsz;i++)
fprintf(fp,"%.3f inch %.3f inch lineto\n",MAP_PSX(x[i]), MAP_PSY(y[i]));
}
return;
}
The next two functions combined with the sd_Polygon() function allow us to create arcs on Amiga
Screens/Windows. The loc_pieline() function calls the sin() and cos() trigonometric functions. On a
non-FPU system, these calls can significantly degrade performance. Therefore, it may be wise to create
a lookup table containing trigonometric values. Values of the sin() and cos() functions at specific angles
would be obtained from the table instead of re-calculating the values each time.
BOOL sd_arc(float xloc, float yloc, float rad, float angstart,
float angend, float aspect, USHORT l_color){
int i=0, j=0;
float *xpie=NULL,*ypie=NULL, tmp=0.0, incr=1.0;
float xtmp=0.0, ytmp=0.0, endang=0.0;
tmp=floor(angstart);
endang=floor(angend);
if(endang==359.0) endang=360.0;
if(Output_Device==SCREEN){
xpie=malloc(740 * sizeof(float));
ypie=malloc(740 * sizeof(float));
if(!xpie || !ypie){
if(xpie) free(xpie);
if(ypie) free(ypie);
return(FALSE);
}
/* aspect determines how elliptical to make the polygon, this is important when drawing
to a screen that does not have square pixels (e.g. 640x400)
*/
loc_pieline(rad,tmp, aspect, &xtmp, &ytmp);
xpie[0]=MAP_X(xloc); ypie[0]=MAP_Y(yloc);
xpie[1]=MAP_X(xloc+xtmp); ypie[1]=MAP_Y(yloc+ytmp);
i=2;
while(tmp<=endang && i<730){
loc_pieline(rad, tmp, aspect, &xtmp, &ytmp);
xpie[i]=MAP_X(xloc+xtmp);
ypie[i++]=MAP_Y(yloc+ytmp);
tmp += incr;
xtmp=ytmp=0.0;
}
xpie[i]=MAP_X(xloc); ypie[i]=MAP_Y(yloc); /* close slice */
i++;
sd_polygon(xpie, ypie, i, l_color, 0); /*0==line pattern*/
free(xpie); free(ypie);
}
else if(Output_Device==PS){
fprintf(fp,"%.3f setgray\n",((GetRGB4(ssgcm,l_color))/4095.));
/* The init file sd.PSinit, contains a PS procedure called ellipse that takes */
/* the aspect parameter into account. For this example we will use arc */
fprintf(fp,"newpath\n%.3f inch %.3f inch %.3f inch %.3f inch
%.3f %.3f arc\n", MAP_PSX(MAP_X(xloc)), MAP_PSY(MAP_Y(yloc)),
rad,angstart, angend);
}
return(TRUE);
}
#define DTOR 0.017453 /* used to convert from degrees to radians */
void loc_pieline(float radius, float angle, float aspect,float *xt, float *yt){
if(aspect<1.0){
*xt=cos(angle * DTOR) * radius;
*yt=-aspect * sin(angle * DTOR) * radius;
}
else{
*xt=cos(angle * DTOR) * radius/aspect;
*yt=-sin(angle * DTOR) * radius;
}
return;
}
Although we have just scratched the surface of the graphics commands available in the PostScript
language, we have constructed all the functions necessary to create simple geometric shapes. There are a
few more commands that many users will need.
PostScript has three operators that can alter the coordinate system. The translate command moves the
entire coordinate system on a page by a user-specified amount. The rotate command rotates the
coordinate system by a user-specified number of degrees. The scale command alters the scale of the
coordinate units.
The translate command moves the origin of the user coordinate system. For example, if a translate
command were issued that moved the origin (0, 0) up one inch and to the right one inch, a subsequent
graphics operation draws the object one inch up and to the right of where expected.
The translate command takes two parameters, an X and a Y value as demonstrated on the following line.
72 72 translate
Now that we know the structure of the translate command, we can demonstrate how it works.
newpath
1 inch 1 inch moveto
2 inch 2 inch lineto
stroke
1 inch 1 inch translate
2 inch 2 inch moveto
3 inch 2 inch lineto
stroke
showpage
The first line is drawn as expected. It begins at one inch up and one inch to the right of the lower left
corner of the page and extends to the two inch, two inch location. Our second line should have started at
the end of our first line (2 inch 2 inch moveto), but it actually started at the three inch, three inch mark.
Basically, our entire page coordinate system was moved. Now when we draw a point at a location such
as 1 inch 1 inch, the point is actually drawn at a location 1 inch up and 1 inch to the right of expected (2
inch 2 inch).
Please note that the translate operation only affects all commands after the translate operation. In
addition, subsequent translate operations are additive. Therefore, if we enter two translate commands
such as the following:
1 inch 1 inch translate
1 inch 1 inch translate
our coordinate system is moved up 2 inches and to the right 2 inches.
The translate command makes it easy to draw complex patterns or drawings multiple times on a page
with only a few commands. If a complex shape were made into a definition, an entire page could be
tiled with that shape. Assume a complex shape that has the definition name "butterfly". We could tile
the entire page by placing two lines of PostScript code inside a loop.
xloc yloc translate
butterfly
Another use for the translate command is to place an entire graph or drawing that is smaller than the
physical page at a specific location on that larger page. In addition, the translate command in
combination with the rotate command allows us to alter the page orientation from Portrait to Landscape.
8.5 inch 0 inch translate
90 rotate
As just demonstrated, rotate is another very useful PostScript command. It rotates the user coordinate
system a specified number of degrees in the counterclockwise direction. The command 90 rotate would
change a line that should have been drawn as a horizontal line to one that is drawn vertically. Rotate
affects all graphics operators after the command is entered. The rotate command is also cumulative as
is the translate command. Two rotate commands such as
60 rotate
60 rotate
would rotate the user coordinate system by a total of 120 degrees.
Another very useful PostScript command is the scale command. This command enables the programmer
to expand or contract almost any object. For example, if a column graph's dimensions are 8.5 inches
wide by 11 inches high, the scale command makes it a simple matter to reduce the graph to one half the
size. If a scale command of 0.5 0.5 scale is placed before the commands that draw the graph, the graph
is drawn as 4.25 by 5.5 inches in size.
The scale command works equally well on font size. Because the scale command can alter the X scale
and Y scale independently (X Y scale), it is a simple matter to make a normally proportionate font
appear as a short fat font or a tall thin font.
Two very important PostScript commands are gsave and grestore. These two commands save and
restore the current graphics state respectively. A graphics state includes such things as the current path,
point, setgray value, font, coordinate system, and line thickness. These two commands allow us to
change a global setting and only affect one part of a larger drawing. For example, if a programmer
wants to scale the width of a font for only one text string right in the middle of a drawing, the user can
use a gsave, grestore pair.
gsave
1 inch 1 inch moveto
3.0 1.0 scale
(Wide text) show
grestore
Another common use of the gsave, grestore pair is to fill an object and then outline the same object. As
learned previously, the creation of a filled box requires defining the path and then calling the fill
operator. The fill operator erases the current path. Therefore, a subsequent stroke operation generates
an error. If a gsave command is entered prior to the fill operator, it is possible to save the current path so
the stroke operator can use it. A sample code segment is presented.
newpath
0.0 setgray
1 inch 1 inch moveto
1 inch 2 inch lineto
2 inch 2 inch lineto
2 inch 1 inch lineto
closepath
gsave
0.5 setgray
fill
grestore
stroke
showpage
The PostScript examples previously discussed created a drawing immediately. This is not too useful if a
user wants to save a drawing in a computer readable format that can be imported into another program.
Fortunately, PostScript allows us to encapsulate these commands in a file that can be loaded into another
program for inclusion in that program's output. Adobe has appropriately named this encapsulated set of
PostScript commands an Encapsulated PostScript File format (EPSF, or EPS for short).
The creation of an EPS file is simple once the programmer understands the graphics commands. An
EPS file is nothing more than two special lines appended before any graphics commands that defines a
drawing. These two lines are as follows.
%!PS-Adobe-3.0 EPSF-3.0
%%BoundingBox: x1 y1 x2 y2
where x1, y1 describe the lower left corner and x2, y2 describe the upper right corner of a bounding box.
The bounding box parameters inform a program that loads in the EPS file about the size of the drawing
contained in the EPS file. This assists the loading program in displaying and clipping the EPS file
correctly. The bounding box must be large enough that every graphic element in the drawing can fit and
is completely visible inside the box.
The following code is a sample EPS file.
%!PS-Adobe-3.0 EPSF-3.0
%%BoundingBox: 71 71 145 145
/inch {72 mul} def
2 setlinewidth
0.5 setgray
1 inch 1 inch moveto
2 inch 2 inch lineto
stroke
showpage
Note that the bounding box dimensions are in default PostScript user coordinates. Because the
EPS-defining header lines must be the first two lines of the file, any definition such as our /inch
definition cannot be used to describe the size of the bounding box. Another important point to
remember is that graphics elements, such as lines, have thickness. The bounding box must take the
thickness of lines into account. If we defined the bounding box to be 72 72 144 144, we may have cut
off part of our two unit thick line.
The PostScript commands presented compose only a small part of the entire PostScript language. The
ways the commands were used and the C functions demonstrated were kept extremely simple for reasons
of clarity. There are more efficient methods to perform almost every operation. For the casual
programmer, ease of implementation is probably a better use of time than worrying about more involved
coding methods that shorten printing time.
Many published texts exist to guide the serious PostScript programmer. I recommend first getting the
PostScript Language Tutorial and Cookbook, Adobe Systems, Inc., Addison-Wesley Publishing. This
text introduces the basics of PostScript coding and has many useful examples. Although not a great text
for the faint of heart, any serious PostScript programmer should also have the PostScript Language
Reference Manual by Adobe Systems, Inc., Addison-Wesley, Second Edition. In addition to the two
texts created by Adobe Systems, Adobe has a developer support group for the professional PostScript
programmer (415/961-4111).
I have created a very simple program called StructDraw.c that can create and send shapes to an Amiga
window, a PostScript device, an HP-GL/2 capable plotter, or save the shapes as a DXF wireframe
drawing. A description of the HP-GL/2 code appears in the next article. The code in StructDraw.c was
kept as simple as possible for clarity sake. To keep the code short, the program does not support any
user interaction. The user will have to alter and recompile the code if they want to send the output to a
different location or change the shapes drawn. StructDraw.c currently sends Amiga output to an Amiga
window. The three other supported formats are saved as files in RAM using the following names:
tmp.ps, tmp.hpgl, and tmp.dxf. After the program creates the four drawings it exits. The interested
programmer is free to use the graphics functions in any code they choose as long as the code is not
included verbatim in a commercial product.